package cn.xiaolang.thread.queue;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;

import java.util.Date;
import java.util.concurrent.*;

/**
 * DelayQueue是一个无界阻塞队列，只有在延迟期满时才能从中提取元素。
 * 该队列的头部是延迟期满后保存时间最长的Delayed 元素
 * 场景：
 * 缓存系统的设计，缓存中的对象，超过了空闲时间，需要从缓存中移出
 */
@Slf4j
public class DelayQueueStarted {

    public static void main(String[] args) {

        DelayQueue<DelayMessage> queue = new DelayQueue<>();
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        for (int i = 0; i < 5; i++) {
            final int index = i;
            // 5个元素，依次存活1-5秒
            final int ttl = i + 1;
            executorService.submit(() -> {
                DelayMessage msg = new DelayMessage(DateUtils.addSeconds(new Date(), ttl), "MSG" + index);
                queue.add(msg);
                log.info("put into the delay queue:{},TTL={}(s)", msg.getContent(), msg.getDelay(TimeUnit.SECONDS));
            }, "p" + i);
        }
        // 1-5s取出所有元素
        new Thread(() -> {
            while (!queue.isEmpty()) {
                try {
                    DelayMessage take = queue.take();
                    log.info("take and remove from delay queue:{}", take.getContent());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "c1").start();

        executorService.shutdown();
    }

    /**
     * 为了具有调用行为，存放到DelayDeque的元素必须继承Delayed接口。
     * Delayed接口使对象成为延迟对象，它使存放在DelayQueue类中的对象具有了激活日期
     */
    static class DelayMessage implements Delayed {
        private Date startDate;

        @Getter
        private String content;

        public DelayMessage(Date startDate, String content) {
            super();
            this.startDate = startDate;
            this.content = content;
        }

        @Override
        public int compareTo(Delayed o) {
            long result = this.getDelay(TimeUnit.NANOSECONDS)
                    - o.getDelay(TimeUnit.NANOSECONDS);
            if (result < 0) {
                return -1;
            } else if (result > 0) {
                return 1;
            } else {
                return 0;
            }
        }

        @Override
        public long getDelay(TimeUnit unit) {
            Date now = new Date();
            long diff = startDate.getTime() - now.getTime();
            return unit.convert(diff, TimeUnit.MILLISECONDS);
        }
    }
}
